﻿using Devil;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Profiling;

namespace GameToolkit.BehaviourTree
{
    public abstract class BehaviourTreeExecutableBase : Disposable, IBehaviourTreeExecutable, IValidator
    {
        public event System.Action<string, EBlackboardAction> OnSetBlackboard;

        GameObject mGameObject;
        BehaviourTreeAsset mAsset;

        BehaviourTreePlayer mPlayer;
        readonly object mLock = new object();
        Dictionary<string, object> mBlackboard = new Dictionary<string, object>();
        ECullingUpdateMode mLastUpdate;
        public ECullingUpdateMode UpdateMode => mLastUpdate;
        public GameObject gameObject => mGameObject;
        public Transform transform => mGameObject == null ? null : mGameObject.transform;

        string mCachedName;
        public string name
        {
            get { return mCachedName; }
            set
            {
                if (mCachedName != value)
                {
                    mCachedName = value;
#if UNITY_EDITOR
                    if (mAsset == null)
                        profiler = $"BT({mCachedName}): <none>";
                    else
                        profiler = $"BT({mCachedName}): {mAsset.Title}";
#endif
                }
            }
        }

#if UNITY_EDITOR
        string profiler;
#endif
        public BehaviourTreeAsset behaviourTreeAsset
        {
            get { return mAsset; }
            set
            {
                if(mAsset != value)
                {
                    mAsset = value;
#if UNITY_EDITOR
                    if (mAsset == null)
                        profiler = $"BT({mCachedName}): <none>";
                    else
                        profiler = $"BT({mCachedName}): {mAsset.Title}";
#endif
                    if (mPlayer != null)
                        mPlayer.SetBehaviourTree(value);
                }
            }
        }

        public BehaviourTreeExecutableBase(GameObject obj) 
        {
            mGameObject = obj;
            mCachedName = obj == null || !ParallelUnity.IsMainThread ? "default" : obj.name;
#if UNITY_EDITOR
            profiler = $"BT({mCachedName}): <none>";
#endif
        }

        public BehaviourTreeExecutableBase(string name)
        {
            mCachedName = name;
#if UNITY_EDITOR
            profiler = $"BT({mCachedName}): <none>";
#endif
        }

        protected override void OnDisposing()
        {
            StopBehaviour();
            mGameObject = null;
            mBlackboard.Clear();
            mAsset = null;
        }

        public virtual T GetComponent<T>()
        {
            if (ParallelUnity.IsMainThread && mGameObject != null)
                return mGameObject.GetComponent<T>();
            else
                return default;
        }

        public BehaviourTreePlayer GetPlayerForAsset(BehaviourTreeAsset asset)
        {
            return mPlayer == null ? null : mPlayer.FindPlayerForAsset(asset);
        }

        protected void StartBehaviour()
        {
            if (mPlayer == null)
            {
#if UNITY_EDITOR
                if (ParallelUtils.IsMainThread)
                {
                    mCachedName = name;
                    if (mAsset == null)
                        profiler = $"BT({mCachedName}): <none>";
                    else
                        profiler = $"BT({mCachedName}): {mAsset.name}";
                }
#endif
                mPlayer = new BehaviourTreePlayer(this);
                mPlayer.SetBehaviourTree(mAsset);
            }
        }

        protected void UpdateBehaviour(float deltaTime, ECullingUpdateMode mode)
        {
            var player = mPlayer;
            if (player != null)
            {
#if UNITY_EDITOR
                Profiler.BeginSample(profiler, mGameObject);
#endif
                mLastUpdate = mode;
                player.UpdateBehaviour(deltaTime);
                mLastUpdate = 0;

#if UNITY_EDITOR
                Profiler.EndSample();
#endif
            }
        }

        protected void StopBehaviour()
        {
            var player = mPlayer;
            mPlayer = null;
            if (player != null)
            {
                player.Dispose();
            }
        }

        #region blackboard
        bool IsValidVarName(string varName)
        {
            if(string.IsNullOrEmpty(varName))
            {
#if UNITY_EDITOR
                var str = ParallelDispatcher.IsMainThread ? name : "BehaviourTreeRunner";
                Debug.LogWarning($"({str}) Invalid blackboard variable name (is empty).", mGameObject);
#endif
                return false;
            }
            return true;
        }

        bool IBehaviourTreeExecutable.IsBlackboardSet(string varName)
        {
            if (IsValidVarName(varName))
            {
                lock (mLock)
                {
                    return mBlackboard.ContainsKey(varName);
                }
            }
            else
            {
                return false;
            }
        }

        void IBehaviourTreeExecutable.SetBlackboard<T>(string varName, T value)
        {
            if (IsValidVarName(varName))
            {
                lock (mLock)
                {
                    mBlackboard[varName] = value;
                }
                OnSetBlackboard?.Invoke(varName, EBlackboardAction.Reset);
            }
        }

        void IBehaviourTreeExecutable.UnsetBlackboard(string varName)
        {
            if (IsValidVarName(varName))
            {
                lock (mLock)
                {
                    if (mBlackboard.ContainsKey(varName))
                        mBlackboard.Remove(varName);
                }
                OnSetBlackboard?.Invoke(varName, EBlackboardAction.Unset);
            }
        }

        bool IBehaviourTreeExecutable.GetBlackboard<T>(string varName, out T value)
        {
            if (IsValidVarName(varName))
            {
                lock (mLock)
                {
                    object v;
                    if (mBlackboard.TryGetValue(varName, out v) && v is T t)
                    {
                        value = t;
                        return true;
                    }
                }
            }
            value = default;
            return false;
        }

        void IBehaviourTreeExecutable.VisitAllValues(System.Action<string, object> visitor)
        {
            if (visitor == null)
                return;
            lock (mLock)
            {
                foreach(var kv in mBlackboard)
                {
                    visitor(kv.Key, kv.Value);
                }
            }
        }

        #endregion

    }
}
